/*	
    Author: Christopher Diggins
    License: MIT Licence 1.0
    Website: http://www.cdiggins.com
*/

#ifndef CVML_SUBROUTINE_HPP
#define CVML_SUBROUTINE_HPP

struct ByteCode;

/// The subroutine class represents a sequence of instructions. 
struct Subroutine
{
    std::string name;
    Instruction* first;
	int callcnt;
	int pushcnt;

    Subroutine(const std::string& _name)
		: callcnt(0), pushcnt(0)
    {
        name = _name;
        first = new Instruction();
        first->comment = "subroutine " + _name;
    }

    ~Subroutine()
    {        
        if (first == NULL) 
            return;
        Instruction* tmp = first->next;
        while (tmp != NULL && tmp->next != NULL) {
            Instruction* tmp2 = tmp->next; 
            delete(tmp);
            tmp = tmp2;
        }
		if (tmp != NULL)
			delete tmp;
    }

	int RefCnt() {
		return pushcnt + callcnt;
	}

    const std::string& GetName()
    {
        return name;
    }

    Instruction* GetLastInstr() 
    {
        Instruction* r = first;
        if (r == NULL) 
            return r;
        while (r->next != NULL)
            r = r->next;
        return r;
    }

	Instruction* AddPushSubInstr(Subroutine* sub) {
		return AddInstruction(op_push_sub, (int)sub, sub->GetName());
	}

	Instruction* AddInstruction(OpCodeEnum opcode = op_noop, int arg = -1, ConstString comment = "") {
		if (first == NULL) {
			first = new Instruction(opcode, arg, comment);
			return first;
		}
		else {
			return GetLastInstr()->AddInstruction(opcode, arg, comment);
		}
	}

	void ClearInstructions() {
		if (first == NULL)
			return;				
		Instruction* prev = first;
		for (Instruction* i = first->next; i != NULL; prev = i, i = i->next) {
			delete prev;
		}
		first = NULL;
	}

	/// Removes all null operations (e.g. comments). 
	/// It is important to remembers that 
    void StripNoops()
    {
        Instruction* i = first;
        while (i != NULL && i->next != NULL) {
            if (i->next->IsNoop()) {
                i->RemoveNext();
            }
            else {
                i = i->next;
            }
        }
        if (first->IsNoop()) {
            first = first->next;
            delete(first->prev);
            first->prev = NULL;
        }

        // HACK: this seems dangerous, the first could be NULL
        // I am not sure that I anticipate this everywhere.
    }

	int GetTotalSize()
	{
		int r = 0;
		for (Instruction* i = first; i != NULL; i = i->next)
			r += i->GetWidth();
		return r;
	}

	void GetVarDefs(std::vector<std::string>& vars)
	{
		for (Instruction* i = first; i != NULL; i = i->next)
			if (i->IsDef())
				vars.push_back(i->comment);
	}

	int GetNumUses(const std::string& var)
	{
		int n = 0;
		// This is actually wrong: 
		// it relies on names that haven't yet been guaranteed to be unique.
		// To be honest I think I should be looking at offsets from the subroutine/
		for (Instruction* i = first; i != NULL; i = i->next) {
			if (i->opcode == op_getN && i->comment == var)
				++n;
			if (i->opcode == op_push_sub) {
				if (i->GetSub()->GetNumUses(var) > 0) 
				{
					n += 1000;
				}
			}
		}
		return n;
	}

	InstrSeq GetVarDef(const std::string& var) {
		// What is the indicator?
		for (Instruction* i = first; i != NULL; i = i->next) {
			if (i->IsDef(var)) {
				Instruction* j = i;
				while (!j->IsInitializer()) {
					j = j->prev;
					Assert(j != NULL);
				}
				return InstrSeq(j, i);
			}
		}
		Error(var + " definition could not be found");
		return InstrSeq(NULL, NULL);
	}

	void ExtractSeq(InstrSeq seq) {
		if (seq.begin == first) {
			first = seq.end->next;
		}
		else {
			Assert(seq.begin->prev != NULL);
			seq.begin->prev->next = seq.end->next;
		}
		seq.begin->prev = NULL;
		seq.end->next = NULL;
	}

	InstrSeq ExtractVarDef(const std::string& var) {
		InstrSeq seq = GetVarDef(var);
		ExtractSeq(seq);
		Assert(seq.begin->IsInitializer());
		seq.DeleteBegin(); // Remove "initializer
		Assert(seq.end->IsDef());
		seq.DeleteEnd(); // Remove "def"
		return seq;
	}

	void DeleteSeq(Instruction* begin, Instruction* last) {
		while (begin != last) {
			begin = begin->next;
			delete begin->prev;
		}
	}

	void RemoveVar(const std::string& var)
	{
		InstrSeq seq = ExtractVarDef(var);
		DeleteSeq(seq.begin, seq.end);
	}

	void InsertSeq(InstrSeq seq, Instruction* i)
	{
		i->prev->next = seq.begin;
		seq.end->next = i;
	}

	void ReplaceWithSeq(InstrSeq seq, Instruction*& i) {
		i->prev->next = seq.begin;
		seq.begin->prev = i->prev;
		seq.end->next = i->next;
		i->next->prev = seq.end;
		delete i;
		i = NULL;
	}

	void ExpandInline(InstrSeq seq, const std::string& var, bool& done) {
		Instruction* i = first; 
		while (i != NULL) {
			if (i->opcode == op_getN && i->comment == var) {
				Assert(!done);
				done = true;
				ReplaceWithSeq(seq, i);
				i = seq.end->next;
			}
			else if (i->opcode == op_push_sub) 
			{
				i->GetSub()->ExpandInline(seq, var, done);
				i = i->next;
			}
			else 
			{
				i = i->next;
			}
		}
	}

	void InlineVar(const std::string& var)
	{
		InstrSeq seq = ExtractVarDef(var);
		bool b = false;
		ExpandInline(seq, var, b);
		Assert(b == true);
	}

 
private:

    Subroutine() { }

};

#endif
